📱 MKV → HEVC Batch Encoder for Smartphone

Windows 11 Batch Script — encode_hevc.bat
🌐 日本語

🎥 Recommended Player: VLC for Smartphone

VLC is a completely free, open-source media player that can decode almost any video format, including the HEVC (H.265) files produced by this script. It provides excellent hardware decoding, reliable subtitle support, and works great on both Android and iOS.

Installing FFmpeg

This script requires FFmpeg (which includes both ffmpeg and ffprobe) installed and available in your Windows PATH. The recommended build for Windows is the Gyan.dev full release, which includes all necessary libraries: libass (subtitle rendering), hevc_amf (AMD), hevc_nvenc (NVIDIA), hevc_qsv (Intel), and libx265 (software).

このスクリプトを使うにはFFmpegffmpegffprobeの両方を含む)がWindowsのPATHに入っている必要があります。Windows向けにおすすめなのはGyan.devのfullリリースで、必要なライブラリ(libass、hevc_amf、hevc_nvenc、hevc_qsv、libx265)が全部入ってます。

Option 1 — winget (Recommended)

Open Command Prompt or PowerShell and run:

コマンドプロンプトまたはPowerShellを開いて実行:

winget install Gyan.FFmpeg

After installation, close and reopen your terminal to refresh the PATH, then verify with:

インストール後、ターミナルを閉じて再度開いてPATHを更新し、確認コマンドを実行:

ffmpeg -version
✔ Why winget? winget is built into Windows 11 and handles PATH setup automatically — no manual extraction or environment variable editing needed. Future updates are easy too: winget upgrade Gyan.FFmpeg

Option 2 — Manual Install

  1. Go to gyan.dev/ffmpeg/builds and download the release full ZIP — not "essentials", as the full build includes libass for subtitle rendering.
  2. Extract the ZIP to a permanent location such as C:\ffmpeg\.
  3. Add C:\ffmpeg\bin to your system PATH via System Properties → Environment Variables → Path → Edit → New.
  4. Open a new Command Prompt and run ffmpeg -version to confirm.

Download the Batch File

Download encode_hevc.bat and place it directly inside the folder containing your .mkv files. The script will automatically process every single .mkv file in that same folder, so make sure the folder only contains the files you want to convert.

encode_hevc.batをダウンロードして、.mkvファイルが入っているフォルダに直接置いてください。スクリプトはそのフォルダ内の全.mkv ファイルを自動的に処理します。変換したいファイルだけそのフォルダに入れておいてください。

What This Script Does

encode_hevc.bat is a Windows batch script that converts all MKV files in the same folder into smartphone-optimised MP4 files using HEVC (H.265) encoding. It is designed for watching anime and Japanese media on smartphones using a player such as VLC.

encode_hevc.batは、同じフォルダ内の全MKVファイルをスマートフォン最適化MP4に一括変換するWindowsバッチスクリプトです。VLCなどのプレイヤーでスマートフォン上でアニメや日本語メディアを視聴することを目的として設計されています。

Setting Value Notes
Video CodecHEVC / H.265Hardware (GPU) or software
Video QualityQP 31Good balance of quality and file size
Output Resolution1280×720 (16:9)
960×720 (4:3)
Auto-detected from source
Scale FilterLanczosHigh-quality downscaling
Sharpeningunsharp 3:3:0.3Subtle post-downscale crispness
Audio CodecAAC-LC stereoUniversal smartphone compatibility
Audio Bitrate128 kbpsGood quality for voice/music on mobile
Audio TrackJapanese (jpn) onlyAuto-selects first/default Japanese track
SubtitlesBurned inEnglish → default → first (auto fallback)
ChaptersPreservedOpening/ending markers carried over
MP4 Taghvc1Required for Apple device playback
FaststartEnabledInstant playback start, great for Wi-Fi streaming
Output Folderencoded_output\Created automatically in the script's folder

How to Use

  1. Install FFmpeg using winget or manually as described above.
  2. Place encode_hevc.bat inside the same folder as your MKV files. It will encode every .mkv it finds — the script does not recurse into subfolders.
  3. Double-click encode_hevc.bat in Windows Explorer. A Command Prompt window will open.
  4. A GPU selection menu will appear. Type the number for your hardware (1–4) and press Enter.
  5. The script analyses and encodes each file one by one, showing progress in the console.
  6. Encoded files appear in the encoded_output\ subfolder, ready to transfer to your smartphone.

GPU / Encoder Options

When you run the script you will see this menu:

スクリプトを実行するとこのメニューが表示されます:

  Select your GPU / encoder:

    1. AMD      (hevc_amf   - VCE hardware)
    2. NVIDIA   (hevc_nvenc - NVENC hardware)
    3. Intel    (hevc_qsv   - Quick Sync hardware)
    4. Software (libx265    - slow but universal)
# Encoder GPU Required Quality Mode Speed
1 AMD hevc_amf AMD RX 400 series or newer CQP 31 Very fast ⚡
2 NVIDIA hevc_nvenc GTX 950 or newer constqp 31 Very fast ⚡
3 INTEL hevc_qsv Intel 6th gen (Skylake) iGPU or newer ICQ 31 Fast ⚡
4 SOFTWARE libx265 None — CPU only CRF 22 Slow 🐢
ℹ Quality equivalence across encoders All four options are calibrated for approximately equivalent visual quality. Hardware encoders (AMD/NVIDIA/Intel) sacrifice a small amount of efficiency for dramatically faster encoding. The software encoder (libx265 CRF 22) produces the smallest files at the same quality but may take 10–20× longer.

Detailed Feature Breakdown

🎞 Automatic Aspect Ratio Detection

Before encoding, ffprobe reads the stored pixel dimensions and the Sample Aspect Ratio (SAR) to calculate the true display width. It then picks the correct output resolution:

エンコード前にffprobeが保存されたピクセル寸法とサンプルアスペクト比(SAR)を読み取り、実際の表示幅を計算して正しい出力解像度を選びます:

Display AspectOutputTypical Source
16:91280×720BD/HD anime, modern TV
4:3960×720DVD anime, older TV releases
Otherauto width × 720Fallback for unusual sources

🔤 Automatic Subtitle Selection (3-tier fallback)

  1. English-taggedfinds the subtitle stream with language=eng metadata.
  2. Default-flaggedselects the subtitle stream marked disposition:default=1 by the file author.
  3. First availablelast resort: uses the first subtitle track (s:0) regardless of language.

If a file has no subtitle tracks at all, it is skipped with a warning in the console.

字幕トラックが全くないファイルは警告付きでスキップされます。

🔊 Japanese Audio Selection

Only the Japanese (jpn) audio track is mapped to the output — commentary tracks, dubs, and other languages are automatically discarded. The script first looks for a default-flagged Japanese track, then falls back to the first Japanese track. If no Japanese track exists, the first audio track is used with a warning.

日本語(jpn)音声トラックのみが出力にマッピングされます。コメンタリートラック、吹き替え、その他の言語は自動的に除外されます。まずデフォルトフラグ付きの日本語トラックを探し、なければ最初の日本語トラックにフォールバック。日本語トラックが存在しない場合は警告付きで最初の音声トラックが使用されます。

🎨 Video Quality Pipeline

Lanczos scaling — high-quality downscale algorithm that preserves sharpness better than the default bilinear filter. Applied before subtitle burn-in.

ランチョスダウンスケール — デフォルトのバイリニアフィルターより鮮明さを保持する高品質ダウンスケールアルゴリズム。字幕焼き込み前に適用。

Unsharp mask (3:3:0.3) — subtle sharpening applied after scaling to recover crispness lost during downscaling. Conservative values to avoid artefacts.

アンシャープマスク(3:3:0.3) — ダウンスケール後に失われる鮮明さを回復するための控えめなシャープニング。アーティファクトを避けるため控えめな値に設定。

Subtitle burn-in (hardsubbing) — subtitles are permanently rendered into the video frames using libass, the same renderer used by mpv and VLC. Embedded fonts from the MKV are used automatically.

字幕焼き込み(ハードサブ) — mpvやVLCと同じ高品質レンダラーlibassを使って字幕を映像フレームに永続的にレンダリング。MKVに埋め込まれたフォントも自動的に使用。

Chapters, title metadata, hvc1 tag, and faststart — chapter markers are preserved from the source MKV; the title tag is set from the filename; the hvc1 codec tag ensures correct playback on Apple devices; faststart moves metadata to the front of the file for instant playback.

チャプター、タイトルメタデータ、hvc1タグ、ファストスタート — チャプターマーカーはソースMKVから保持。タイトルタグはファイル名から設定。hvc1コーデックタグはAppleデバイスでの正しい再生を確保。ファストスタートはメタデータをファイルの先頭に移動して即時再生を実現。

Smartphone Compatibility

This script is optimised for playback on smartphones using VLC for iOS / Android. Output settings were specifically tested on an iPhone 11 with VLC.

このスクリプトはVLC for iOS / Androidでのスマートフォン再生に最適化されています。出力設定はiPhone 11でVLCを使って具体的にテストされています。

Device / AppCompatibilityNotes
iPhone 11 + VLCExcellentHardware HEVC decode, directly tested
iPhone 11 + Native PlayerGoodhvc1 tag required (included)
iPhone 12+ExcellentHardware HEVC decode
Android + VLCExcellentHEVC hardware decode on most modern Android
Android Native GalleryVariesSamsung/Pixel generally fine
ℹ Why 8-bit instead of 10-bit? Many source files (especially Hi10P anime) are 10-bit. While technically supported, 10-bit HEVC can display with washed-out, faded colours on devices like the iPhone 11 when played via VLC. Converting to 8-bit (yuv420p) ensures correct, vivid colours on all devices with no loss in perceived quality at 720p.
✔ Expected file sizes At QP 31 with 128kbps audio, a typical 24-minute anime episode at 1280×720 will be approximately 150–250 MB, depending on animation complexity. This is a major reduction from typical 600 MB–1 GB MKV sources, making it practical to carry a full season on your phone.

Full Batch File Code

Copy and paste the code below into a new text file, then save it as encode_hevc.bat. In Notepad, set "Save as type" to "All Files (*.*)" to prevent it saving as .txt.

以下のコードをコピーして新しいテキストファイルに貼り付け、encode_hevc.batとして保存してください。メモ帳では「ファイルの種類」を「すべてのファイル (*.*)」に変更して.txtとして保存されないようにしてください。

⚠ Run via cmd.exe — NOT PowerShell Always launch this script by double-clicking it in Windows Explorer, or by running it from a cmd.exe window. Running from PowerShell will cause the loop variables to fail and nothing will encode.
@echo off
setlocal enabledelayedexpansion

:: ============================================================
::  Universal MKV → HEVC 8-bit MP4 Batch Encoder
::  Supports: AMD VCE, NVIDIA NVENC, Intel QSV, Software (x265)
::  Audio:     First Japanese track only → AAC 128kbps stereo
::  Subtitles: Burned in with fallback priority:
::               1. English-tagged (eng)
::               2. Default-flagged subtitle in MKV
::               3. First available subtitle track
::               4. Skip file if no subtitles exist
::  Aspect:    4:3 source → 960x720 / 16:9 source → 1280x720
::  Chapters:  Preserved
::  Metadata:  Title set from filename
::
::  IMPORTANT: Run by double-clicking in Explorer or via cmd.exe
::             Do NOT run from PowerShell.
::
::  Requirements: ffmpeg + ffprobe must be in PATH
:: ============================================================

:: --- SETTINGS -----------------------------------------------

set QP=31
set AUDIO_BITRATE=128k
set OUTPUT_DIR=encoded_output
set SHARPEN=3:3:0.3

:: -------------------------------------------------------

:: ================================================================
:: GPU SELECTION MENU
:: Equivalent quality settings across encoders matching CQP 31:
::   AMD    — hevc_amf,   -rc cqp       qp 31
::   NVIDIA — hevc_nvenc, -rc constqp   qp 31
::   Intel  — hevc_qsv,   -q 31         (ICQ mode)
::   x265   — libx265,    -crf 22       (CRF 22 ≈ CQP 31 for HEVC)
:: ================================================================

echo ============================================================
echo  Universal HEVC 8-bit Encoder
echo  QP=31  Audio=128kbps  4:3=960x720  16:9=1280x720
echo ============================================================
echo.
echo  Select your GPU / encoder:
echo.
echo    1. AMD      ^(hevc_amf   - VCE hardware^)
echo    2. NVIDIA   ^(hevc_nvenc - NVENC hardware^)
echo    3. Intel    ^(hevc_qsv   - Quick Sync hardware^)
echo    4. Software ^(libx265    - slow but universal^)
echo.
set /p GPU_CHOICE="  Enter choice (1-4): "

if "%GPU_CHOICE%" == "1" (
    set ENCODER=hevc_amf
    set ENC_LABEL=AMD VCE
    set ENC_PARAMS=-rc cqp -qp_i %QP% -qp_p %QP% -qp_b %QP% -quality quality -profile:v main -pix_fmt yuv420p
)
if "%GPU_CHOICE%" == "2" (
    set ENCODER=hevc_nvenc
    set ENC_LABEL=NVIDIA NVENC
    set ENC_PARAMS=-rc constqp -qp %QP% -preset p4 -profile:v main -pix_fmt yuv420p
)
if "%GPU_CHOICE%" == "3" (
    set ENCODER=hevc_qsv
    set ENC_LABEL=Intel Quick Sync
    set ENC_PARAMS=-q %QP% -preset slower -profile:v main -pix_fmt nv12
)
if "%GPU_CHOICE%" == "4" (
    set ENCODER=libx265
    set ENC_LABEL=Software x265
    set ENC_PARAMS=-crf 22 -preset slow -profile:v main -pix_fmt yuv420p
)

if not defined ENCODER (
    echo.
    echo  Invalid choice. Please enter 1, 2, 3 or 4.
    pause
    exit /b 1
)

echo.
echo  Using encoder: %ENC_LABEL% ^(%ENCODER%^)
echo  Output folder: %OUTPUT_DIR%
echo ============================================================
echo.

if not exist "%OUTPUT_DIR%" mkdir "%OUTPUT_DIR%"

set TMPFILE=%TEMP%\ffprobe_tmp.txt

set ENCODED=0
set FAILED=0
set SKIPPED=0

for %%F in (*.mkv) do (
    echo -------------------------------------------------------
    echo [%%F] Analysing streams...

    :: ================================================================
    :: SUBTITLE SELECTION — three-tier fallback
    :: ================================================================
    set SUB_INDEX=
    set SUB_METHOD=
    set SUB_REL_COUNTER=0

    ffprobe -v error -select_streams s -show_entries stream=index,disposition=default:stream_tags=language -of csv=p=0 "%%F" > "%TMPFILE%" 2>&1

    :: ---- Tier 1: English-tagged subtitle ----
    for /f "usebackq delims=" %%S in ("%TMPFILE%") do (
        if not defined SUB_INDEX (
            set LINE=%%S
            echo !LINE! | findstr /i ",eng" >nul 2>&1
            if !errorlevel! == 0 (
                set SUB_INDEX=!SUB_REL_COUNTER!
                set SUB_METHOD=English-tagged
            )
            set /a SUB_REL_COUNTER+=1
        ) else (
            set /a SUB_REL_COUNTER+=1
        )
    )

    :: ---- Tier 2: Default-flagged subtitle ----
    if not defined SUB_INDEX (
        set SUB_REL_COUNTER=0
        for /f "usebackq delims=" %%S in ("%TMPFILE%") do (
            if not defined SUB_INDEX (
                set LINE=%%S
                echo !LINE! | findstr /r ",1," >nul 2>&1
                if !errorlevel! == 0 (
                    set SUB_INDEX=!SUB_REL_COUNTER!
                    set SUB_METHOD=default-flagged
                )
            )
            set /a SUB_REL_COUNTER+=1
        )
    )

    :: ---- Tier 3: First available subtitle ----
    if not defined SUB_INDEX (
        for /f "usebackq delims=" %%S in ("%TMPFILE%") do (
            if not defined SUB_INDEX (
                set SUB_INDEX=0
                set SUB_METHOD=first available
            )
        )
    )

    :: ================================================================
    :: All remaining processing in if/else — no goto inside loop
    :: ================================================================
    if not defined SUB_INDEX (
        echo [%%F] WARNING: No subtitle tracks found. Skipping file.
        set /a SKIPPED+=1
    ) else (
        echo [%%F] Subtitle selected: index !SUB_INDEX! ^(!SUB_METHOD!^)

        :: ================================================================
        :: RESOLUTION — 4:3 → 960x720, 16:9 → 1280x720
        :: ================================================================
        set STORED_W=
        set STORED_H=
        set SAR_N=1
        set SAR_D=1

        ffprobe -v error -select_streams v:0 -show_entries stream=width -of default=nw=1:nk=1 "%%F" > "%TMPFILE%" 2>&1
        for /f "usebackq tokens=1" %%V in ("%TMPFILE%") do if not defined STORED_W set STORED_W=%%V

        ffprobe -v error -select_streams v:0 -show_entries stream=height -of default=nw=1:nk=1 "%%F" > "%TMPFILE%" 2>&1
        for /f "usebackq tokens=1" %%V in ("%TMPFILE%") do if not defined STORED_H set STORED_H=%%V

        ffprobe -v error -select_streams v:0 -show_entries stream=sample_aspect_ratio -of default=nw=1:nk=1 "%%F" > "%TMPFILE%" 2>&1
        for /f "usebackq tokens=1,2 delims=:" %%A in ("%TMPFILE%") do (
            if not "%%A" == "N/A" if not "%%A" == "0" (
                set SAR_N=%%A
                set SAR_D=%%B
            )
        )

        if "!SAR_D!" == "0" set SAR_D=1
        if "!SAR_N!" == "0" set SAR_N=1

        set /a DISP_W=!STORED_W! * !SAR_N! / !SAR_D!

        set SCALE_FILTER=scale=1280:720:flags=lanczos
        set RES_LABEL=1280x720 ^(16:9 default^)

        if defined DISP_W if defined STORED_H (
            set /a RATIO10=!DISP_W! * 10 / !STORED_H!
            if !RATIO10! GEQ 14 (
                set SCALE_FILTER=scale=1280:720:flags=lanczos
                set RES_LABEL=1280x720 ^(16:9^)
            ) else (
                set SCALE_FILTER=scale=960:720:flags=lanczos
                set RES_LABEL=960x720 ^(4:3^)
            )
        )

        echo [%%F] Display: !DISP_W!x!STORED_H! -^> !RES_LABEL!

        :: ================================================================
        :: JAPANESE AUDIO — find first/default jpn track, map by index
        :: ================================================================
        set JPN_ABS=
        set JPN_REL=0
        set JPN_FOUND=

        ffprobe -v error -select_streams a -show_entries stream=index,disposition=default:stream_tags=language -of csv=p=0 "%%F" > "%TMPFILE%" 2>&1

        :: First pass: default-flagged jpn audio
        for /f "usebackq delims=" %%S in ("%TMPFILE%") do (
            if not defined JPN_ABS (
                set LINE=%%S
                echo !LINE! | findstr /i "jpn" >nul 2>&1
                if !errorlevel! == 0 (
                    echo !LINE! | findstr /r ",1" >nul 2>&1
                    if !errorlevel! == 0 (
                        for /f "tokens=1 delims=," %%I in ("!LINE!") do set JPN_ABS=%%I
                    )
                )
            )
        )

        :: Second pass: first jpn audio track
        if not defined JPN_ABS (
            for /f "usebackq delims=" %%S in ("%TMPFILE%") do (
                if not defined JPN_ABS (
                    set LINE=%%S
                    echo !LINE! | findstr /i "jpn" >nul 2>&1
                    if !errorlevel! == 0 (
                        for /f "tokens=1 delims=," %%I in ("!LINE!") do set JPN_ABS=%%I
                    )
                )
            )
        )

        :: Convert absolute index to audio-relative index
        if defined JPN_ABS (
            set JPN_REL=0
            ffprobe -v error -select_streams a -show_entries stream=index -of csv=p=0 "%%F" > "%TMPFILE%" 2>&1
            for /f "usebackq delims=" %%S in ("%TMPFILE%") do (
                if not defined JPN_FOUND (
                    for /f "tokens=1 delims=," %%I in ("%%S") do (
                        if %%I == !JPN_ABS! (
                            set JPN_FOUND=1
                        ) else if not defined JPN_FOUND (
                            set /a JPN_REL+=1
                        )
                    )
                )
            )
            echo [%%F] Japanese audio: stream !JPN_ABS! ^(audio index !JPN_REL!^)
            set JPN_MAP=0:a:!JPN_REL!
        ) else (
            echo [%%F] WARNING: No Japanese audio found, using first audio track.
            set JPN_MAP=0:a:0
        )

        :: ================================================================
        :: ENCODE
        :: ================================================================
        set TITLE=%%~nF
        echo [%%F] Starting encode ^(%ENC_LABEL%^)...

        ffmpeg -y ^
            -i "%%F" ^
            -vf "!SCALE_FILTER!,subtitles='%%F':si=!SUB_INDEX!,unsharp=%SHARPEN%" ^
            -c:v %ENCODER% ^
            %ENC_PARAMS% ^
            -tag:v hvc1 ^
            -movflags +faststart ^
            -c:a aac ^
            -b:a %AUDIO_BITRATE% ^
            -ac 2 ^
            -map 0:v:0 ^
            -map !JPN_MAP! ^
            -map_chapters 0 ^
            -metadata title="!TITLE!" ^
            "%OUTPUT_DIR%\%%~nF.mp4"

        if !errorlevel! == 0 (
            echo [%%F] Done ^> %OUTPUT_DIR%\%%~nF.mp4
            set /a ENCODED+=1
        ) else (
            echo [%%F] FAILED - check output above for errors
            set /a FAILED+=1
        )
    )

    set JPN_ABS=
    set JPN_FOUND=
    set STORED_W=
    set STORED_H=
    set SAR_N=1
    set SAR_D=1
    echo.
)

if exist "%TMPFILE%" del "%TMPFILE%"

echo ============================================================
echo  Finished!  Encoder: %ENC_LABEL%
echo  Encoded: %ENCODED%   Failed: %FAILED%   Skipped: %SKIPPED%
echo ============================================================
pause